package com.quectel.business.common.propertybill;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.func.LambdaUtil;
import com.quectel.base.tuple.Tuple2;
import com.quectel.base.tuple.Tuple3;
import com.quectel.base.tuple.Tuples;
import com.quectel.business.common.mobileuser.MobileUserMsgBusiness;
import com.quectel.business.common.propertybill.ex.CalPropertyBillException;
import com.quectel.business.common.propertybill.ex.GetBillLengthErrorException;
import com.quectel.constant.PushConstants;
import com.quectel.constant.core.household.HouseholdConstants;
import com.quectel.constant.core.invoice.InvoiceConstants;
import com.quectel.constant.core.mobileuser.MobileUserMsgConstants;
import com.quectel.constant.core.propertybill.PropertyBillConstants;
import com.quectel.constant.core.propertybill.PropertyChargeableConstant;
import com.quectel.constant.global.SystemConstants;
import com.quectel.core.module.household.dto.HouseholdDto;
import com.quectel.core.module.household.dto.HouseholdRoomDto;
import com.quectel.core.module.household.service.HouseholdRoomService;
import com.quectel.core.module.mobileuser.dto.MobileUserDto;
import com.quectel.core.module.mobileuser.service.MobileUserService;
import com.quectel.core.module.propertybill.dto.*;
import com.quectel.core.module.propertybill.service.*;
import com.quectel.core.module.propertybill.vo.PropertyBillAmountVo;
import com.quectel.core.module.propertybill.vo.PropertyBillCycleDateInfoVo;
import com.quectel.core.module.propertybill.vo.PropertyChargeableItemAmountVo;
import com.quectel.core.module.propertybill.vo.PropertyChargeableListActivityVo;
import com.quectel.core.module.village.dto.VillageRoomDto;
import com.quectel.core.module.village.service.VillageRoomService;
import com.quectel.util.common.JacksonUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

/**
 * @author zhangheng
 * @email nolan.zhang@quectel.com
 * @date 2023/4/13 9:42
 * @description 物业账单相关
 */
@Component
@Slf4j
public class PropertyBillBusiness {
    /**
     * 最后计算结果的精度
     */
    public static final int SCALE = 2;

    @DubboReference
    private VillageRoomService villageRoomService;
    @DubboReference
    private PropertyBillService propertyBillService;
    @DubboReference
    private PropertyBillActivityService propertyBillActivityService;
    @DubboReference
    private PropertyChargeableListActivityService propertyChargeableListActivityService;
    @DubboReference
    private PropertyChargeableListService propertyChargeableListService;
    @DubboReference
    private PropertyChargeableItemService propertyChargeableItemService;
    @DubboReference
    private PropertyChargeableListItemBindingService propertyChargeableListItemBindingService;
    @DubboReference
    private HouseholdRoomService householdRoomService;
    @DubboReference
    private MobileUserService mobileUserService;


    @Autowired
    private MobileUserMsgBusiness mobileUserMsgBusiness;

    public void sendPropertyBillMsg(Long billId, List<Byte> pushTypes, Byte msgType) {

        PropertyBillDto propertyBillDto = propertyBillService.selectById(billId);
        if (propertyBillDto == null) {
            return;
        }

        if (CollectionUtils.isEmpty(pushTypes)) {
            return;
        }

        List<PushConstants.PushType> pushTypeEnums = new ArrayList<>();
        for (Byte aByte : pushTypes) {
            PushConstants.PushType byType = PushConstants.PushType.getByType(aByte);
            if (byType != null) {
                pushTypeEnums.add(byType);
            }
        }
        if (CollectionUtils.isEmpty(pushTypes)) {
            return;
        }

        //查询房间业主
        List<HouseholdRoomDto> householdRoomDtos = householdRoomService.selectByRoomIdAndIdentityTypes(propertyBillDto.getRoomId(), Arrays.asList(HouseholdConstants.IDENTITY_CATEGORY_OWNER, HouseholdConstants.IDENTITY_CATEGORY_FAMILY));

        if (CollectionUtils.isEmpty(householdRoomDtos)) {
            return;
        }
        // 查询注册mobileUser
        List<MobileUserDto> mobileUserDtoList = new ArrayList<>();
        for (HouseholdRoomDto householdRoomDto : householdRoomDtos) {
            HouseholdDto householdDto = householdRoomDto.getHouseholdDto();
            if (householdDto == null) {
                continue;
            }
            //查询mobileUser
            MobileUserDto mobileUserDto = mobileUserService.selectByMobile(householdDto.getMobile());
            if (mobileUserDto != null) {
                mobileUserDtoList.add(mobileUserDto);
            }
        }

        if (CollectionUtils.isNotEmpty(mobileUserDtoList)) {
            String content = "";
            String title = "";
            if (PropertyBillConstants.MsgType.ADD.getType().equals(msgType)) {
                content = getBillContent(
                        propertyBillDto.getRoomId(),
                        propertyBillDto.getStartDate(),
                        propertyBillDto.getEndDate(),
                        propertyBillDto.getChargeableListName()
                );
                title = "【物业缴费】您有新的账单！";
            } else if (PropertyBillConstants.MsgType.OVERDUE.getType().equals(msgType)) {
                content = getOverdueBillContent(
                        propertyBillDto.getRoomId(),
                        propertyBillDto.getStartDate(),
                        propertyBillDto.getEndDate(),
                        propertyBillDto.getChargeableListName(),
                        propertyBillDto.getOverdueDays());
                title = "【物业缴费】您的账单已逾期！";
            }
            //房间 账单开始结束日期 账单名称 已逾期 请尽快到物业缴费中缴纳
            mobileUserMsgBusiness.pushMsg(
                    propertyBillDto.getTenantId(),
                    propertyBillDto.getVillageId(),
                    mobileUserDtoList,
                    MobileUserMsgConstants.SourceType.PROPERTY_BILL_MSG,
                    propertyBillDto.getId(),
                    title,
                    content,
                    true,
                    pushTypeEnums.toArray(new PushConstants.PushType[0])
            );
            log.debug("PropertyBillBusiness sendPropertyBillMsg billId :{},userCount:{}", billId, mobileUserDtoList.size());
        }

    }

    public String getBillContent(Long roomId, Date startDate, Date endDate, String chargeableListName) {
        VillageRoomDto villageRoomDto = villageRoomService.selectCacheById(roomId);
        StringBuilder content = new StringBuilder();
        if (villageRoomDto != null) {
            content.append(villageRoomDto.getVillageName()).append(villageRoomDto.getBuildingName()).append(villageRoomDto.getFloorName()).append(villageRoomDto.getName());
        }
        content.append("【").append(com.quectel.util.common.DateUtils.format(startDate, com.quectel.util.common.DateUtils.FormatType.SIMPLE)).append("~").append(com.quectel.util.common.DateUtils.format(endDate, com.quectel.util.common.DateUtils.FormatType.SIMPLE)).append("】").append("期");
        content.append(chargeableListName).append("账单已生成");
        content.append("请尽快到\"物业缴费\"中缴纳");
        return content.toString();
    }

    public String getOverdueBillContent(Long roomId, Date startDate, Date endDate, String chargeableListName, Long overdueDays) {
        VillageRoomDto villageRoomDto = villageRoomService.selectCacheById(roomId);
        StringBuilder content = new StringBuilder();
        if (villageRoomDto != null) {
            content.append(villageRoomDto.getVillageName()).append(villageRoomDto.getBuildingName()).append(villageRoomDto.getFloorName()).append(villageRoomDto.getName());
        }
        content.append("【").append(com.quectel.util.common.DateUtils.format(startDate, com.quectel.util.common.DateUtils.FormatType.SIMPLE)).append("~").append(com.quectel.util.common.DateUtils.format(endDate, com.quectel.util.common.DateUtils.FormatType.SIMPLE)).append("】").append("期");
        content.append(chargeableListName).append("账单已逾期").append(overdueDays).append("天");
        content.append("请尽快到\"物业缴费\"中缴纳");
        return content.toString();
    }

    /**
     * 生成赠品领取记录
     *
     * @param propertyBillDto
     * @return
     */
    public List<PropertyBillActivityDto> genActivityRecord(PropertyBillDto propertyBillDto, List<PropertyChargeableListActivityVo> giftList) {
        if (CollectionUtils.isEmpty(giftList)) {
            return null;
        }
        List<PropertyBillActivityDto> propertyBillActivityDtos = new ArrayList<>();
        for (PropertyChargeableListActivityVo propertyChargeableListActivityVo : giftList) {
            PropertyBillActivityDto propertyBillActiviyDto = new PropertyBillActivityDto();
            propertyBillActiviyDto.setTenantId(propertyBillDto.getTenantId());
            propertyBillActiviyDto.setVillageId(propertyBillDto.getVillageId());
            propertyBillActiviyDto.setRoomId(propertyBillDto.getRoomId());
            propertyBillActiviyDto.setBillId(propertyBillDto.getId());
            propertyBillActiviyDto.setActivityName(propertyChargeableListActivityVo.getName());
            propertyBillActiviyDto.setConfirmStatus(SystemConstants.ABNORMAL);
            propertyBillActivityDtos.add(propertyBillActiviyDto);
        }

        return propertyBillActivityDtos;
    }


    /**
     * 根据更新后的信息重新生成账单信息
     *
     * @param propertyBillAmountVo
     * @return
     */
    public PropertyBillAmountVo getEditedPropertyBill(PropertyBillAmountVo propertyBillAmountVo) {
        //收费标准
        List<PropertyChargeableItemAmountVo> chargeableItemAmountVos = propertyBillAmountVo.getChargeableItemAmountVos();
        if (CollectionUtils.isEmpty(chargeableItemAmountVos)) {
            return propertyBillAmountVo;
        }
        //覆盖收费项目项
        List<PropertyChargeableItemDto> propertyChargeableItemDtos = new ArrayList<>();
        //收费活动
        List<PropertyChargeableListActivityVo> propertyChargeableListActivityVos = new ArrayList<>(propertyBillAmountVo.getGiftList());
        for (PropertyChargeableItemAmountVo propertyChargeableItemAmountVo : chargeableItemAmountVos) {
            propertyChargeableItemDtos.add(propertyChargeableItemAmountVo.getPropertyChargeableItem());
            if (propertyChargeableItemAmountVo.getPropertyChargeableListActivityVo() != null) {
                propertyChargeableListActivityVos.add(propertyChargeableItemAmountVo.getPropertyChargeableListActivityVo());
            }
        }

        if(propertyBillAmountVo.getPropertyChargeableList()!=null){
            //覆盖收费项目收费相关
            propertyBillAmountVo.getPropertyChargeableList().setItemList(propertyChargeableItemDtos);
        }

        if(propertyBillAmountVo.getPropertyChargeableListActivity()!=null){
            //覆盖赠品
            propertyBillAmountVo.getPropertyChargeableListActivity().setDiscountConfigs(JacksonUtils.toJsonString(propertyChargeableListActivityVos));
        }

        PropertyBillConstants.PropertyChargeableListCycleTypeEnum periodType = PropertyBillConstants.PropertyChargeableListCycleTypeEnum.getByType(propertyBillAmountVo.getPropertyChargeableList().getCycleType());
        if (periodType == null) {
            throw new CalPropertyBillException();
        }
        Date now = new Date();
        //重新设置逾期天数
        if (propertyBillAmountVo.getBillCycleDateInfoVo().getOverdueDate().before(now)) {
            propertyBillAmountVo.getBillCycleDateInfoVo().setOverdueStatus(PropertyBillConstants.OverdueStatus.OVERDUE.getStatus());
            propertyBillAmountVo.getBillCycleDateInfoVo().setOverdueDays(DateUtil.betweenDay(now, propertyBillAmountVo.getBillCycleDateInfoVo().getOverdueDate(), true));
        } else {
            propertyBillAmountVo.getBillCycleDateInfoVo().setOverdueStatus(PropertyBillConstants.OverdueStatus.NOT_OVERDUE.getStatus());
            propertyBillAmountVo.getBillCycleDateInfoVo().setOverdueDays(0L);
        }

        //根据前端传递过来的项目和活动,计算出来新的收费账单
        return calPropertyBillAmount(
                propertyBillAmountVo.getBillCycleDateInfoVo(),
                propertyBillAmountVo.getVillageRoom(),
                propertyBillAmountVo.getPropertyChargeableList(),
                propertyBillAmountVo.getPropertyChargeableListActivity()
        );
    }


    /**
     * 填充收费项目折扣项目名称
     *
     * @param dto
     * @return
     */
    public PropertyChargeableListActivityDto fillInProjectName(PropertyChargeableListActivityDto dto) {
        String discountConfigs = dto.getDiscountConfigs();
        List<PropertyChargeableListActivityVo> propertyChargeableListActivityVos
                = JacksonUtils.parseArray(discountConfigs, PropertyChargeableListActivityVo.class);

        // 填充下项目名
        for (PropertyChargeableListActivityVo vo : propertyChargeableListActivityVos) {
            if (PropertyChargeableConstant.DiscountConfigsType.ITEM_DISCOUNT.getType().equals(vo.getGroupType())) {
                PropertyChargeableItemDto propertyChargeableItemDto = propertyChargeableItemService.selectCacheById(Convert.toLong(vo.getConfigId()));
                if (propertyChargeableItemDto != null) {
                    vo.setName(propertyChargeableItemDto.getName());
                }
            }
        }
        dto.setDiscountConfigs(JacksonUtils.toJsonString(propertyChargeableListActivityVos));
        return dto;
    }


    /**
     * @param chargeableListId 收费标准Id
     * @param villageRoomId    房间Id
     * @param startDate        账单开始日期
     * @param ifUseActivity    是否使用优惠
     * @return
     */
    public PropertyBillAmountVo generatePropertyBill(Long chargeableListId, Long villageRoomId, Date startDate, Boolean ifUseActivity) {

        VillageRoomDto villageRoomDto = villageRoomService.selectById(villageRoomId);
        if (villageRoomDto == null) {
            throw new CalPropertyBillException();
        }

        PropertyChargeableListDto propertyChargeableListDto = propertyChargeableListService.selectById(chargeableListId);
        if (propertyChargeableListDto == null) {
            throw new CalPropertyBillException();
        }

        PropertyBillConstants.PropertyChargeableListCycleTypeEnum periodType = PropertyBillConstants.PropertyChargeableListCycleTypeEnum.getByType(propertyChargeableListDto.getCycleType());
        if (periodType == null) {
            throw new CalPropertyBillException();
        }

        //计算账单时长,计费时长,计费时长单位,逾期时长
        PropertyBillCycleDateInfoVo billCycleDateInfoVo = getBillCycleDateInfo(
                propertyChargeableListDto.getName(),
                startDate,
                periodType,
                propertyChargeableListDto.getOverdueDays());

        PropertyChargeableListActivityDto propertyChargeableListActivityDto = null;
        //非逾期账单才可以享受优惠
        if (Boolean.TRUE.equals(ifUseActivity) && propertyChargeableListDto.getOverdueDays() <= 0) {
            //根据活动查询符合的活动
            List<PropertyChargeableListActivityDto> currentActivity = getCurrentActivity(villageRoomDto.getTenantId(), villageRoomDto.getVillageId(), propertyChargeableListDto.getId(), DateUtil.year(startDate), propertyChargeableListDto.getCycleType(), billCycleDateInfoVo.getPeriodValue());
            if (CollectionUtils.isNotEmpty(currentActivity)) {
                //如果有多个活动,只要一个
                propertyChargeableListActivityDto = fillInProjectName(currentActivity.get(0));
            }
        }

        //查询绑定的收费项目
        List<PropertyChargeableItemDto> itemList = new ArrayList<>();
        Map<String, Object> itemQueryMap = new HashMap<>();
        itemQueryMap.put(LambdaUtil.getFieldName(PropertyChargeableListItemBindingDto::getChargeableListId), propertyChargeableListDto.getId());
        List<PropertyChargeableListItemBindingDto> propertyChargeableListItemBindingDtos = propertyChargeableListItemBindingService.queryList(itemQueryMap);
        for (PropertyChargeableListItemBindingDto listItemBinding : propertyChargeableListItemBindingDtos) {
            PropertyChargeableItemDto propertyChargeableItemDto = propertyChargeableItemService.selectById(listItemBinding.getChargeableItemId());
            itemList.add(propertyChargeableItemDto);
        }

        if (CollectionUtils.isEmpty(itemList)) {
            throw new CalPropertyBillException();
        }

        propertyChargeableListDto.setItemList(itemList);

        //计算
        return calPropertyBillAmount(billCycleDateInfoVo, villageRoomDto, propertyChargeableListDto, propertyChargeableListActivityDto);

    }

    /**
     * 根据计算结果生成数据库模型
     *
     * @param originBill 数据库订单
     * @param newBill    加上逾期和优惠活动的新订单信息
     * @return
     */
    public PropertyBillDto updateOriginBill(PropertyBillDto originBill, PropertyBillDto newBill) {
        //更新账单信息
        originBill.setTotalAmount(newBill.getTotalAmount());
        originBill.setItemAmount(newBill.getItemAmount());
        originBill.setDiscountAmount(newBill.getDiscountAmount());
        originBill.setOverdueAmount(newBill.getOverdueAmount());
        originBill.setOverdueDate(newBill.getOverdueDate());
        originBill.setOverdueDays(newBill.getOverdueDays());
        originBill.setStartDate(newBill.getStartDate());
        originBill.setEndDate(newBill.getEndDate());

        return originBill;
    }

    /**
     * 根据计算结果生成数据库模型
     *
     * @param propertyBillAmountVo 计算结果
     * @return
     */
    public PropertyBillDto genBillByPropertyBillAmountVo(PropertyBillAmountVo propertyBillAmountVo) {
        PropertyBillDto propertyBillDto = new PropertyBillDto();
        propertyBillDto.setTenantId(propertyBillAmountVo.getVillageRoom().getTenantId());
        propertyBillDto.setVillageId(propertyBillAmountVo.getVillageRoom().getVillageId());
        propertyBillDto.setBuildingId(propertyBillAmountVo.getVillageRoom().getBuildingId());
        propertyBillDto.setFloorId(propertyBillAmountVo.getVillageRoom().getFloorId());
        propertyBillDto.setRoomId(propertyBillAmountVo.getVillageRoom().getId());
        propertyBillDto.setChargeableListId(propertyBillAmountVo.getPropertyChargeableList().getId());
        propertyBillDto.setPeriodType(propertyBillAmountVo.getBillCycleDateInfoVo().getPeriodType());
        propertyBillDto.setYear(propertyBillAmountVo.getBillCycleDateInfoVo().getYear());
        propertyBillDto.setPeriodValue(propertyBillAmountVo.getBillCycleDateInfoVo().getPeriodValue());
        propertyBillDto.setPeriodName(propertyBillAmountVo.getBillCycleDateInfoVo().getPeriodName());
        propertyBillDto.setPayStatus(PropertyBillConstants.PayStatus.INIT.getStatusValue());
        propertyBillDto.setInvoiceStatus(InvoiceConstants.InvoiceStatus.NOT_SUBMITTED.getType());
        propertyBillDto.setOverdueDate(propertyBillAmountVo.getBillCycleDateInfoVo().getOverdueDate());
        propertyBillDto.setOverdueDays(propertyBillAmountVo.getBillCycleDateInfoVo().getOverdueDays());
        propertyBillDto.setTotalAmount(propertyBillAmountVo.getTotalAmount());
        propertyBillDto.setItemAmount(propertyBillAmountVo.getItemTotalAmount());
        propertyBillDto.setDiscountAmount(propertyBillAmountVo.getDiscountAmount());
        propertyBillDto.setOverdueAmount(propertyBillAmountVo.getOverdueAmount());
        propertyBillDto.setCreateTime(new Date());
        //填充账单开始和结束日期
        propertyBillDto.setStartDate(propertyBillAmountVo.getBillCycleDateInfoVo().getStartDate());
        propertyBillDto.setEndDate(propertyBillAmountVo.getBillCycleDateInfoVo().getEndDate());

        return propertyBillDto;
    }


    /**
     * 计算收费项目的折扣
     *
     * @param propertyChargeableItemAmountVo   收费项目收费详情
     * @param propertyChargeableListActivityVo 活动配置
     * @return
     */
    public BigDecimal calItemDiscountAmount(PropertyChargeableItemAmountVo propertyChargeableItemAmountVo, PropertyChargeableListActivityVo propertyChargeableListActivityVo) {
        PropertyBillConstants.DiscountType discountType = PropertyBillConstants.DiscountType.getByType(Byte.valueOf(propertyChargeableListActivityVo.getType()));
        if (discountType == null) {
            throw new GetBillLengthErrorException();
        }
        //计算优惠金额
        return discountType.calculate(propertyChargeableItemAmountVo.getItemAmount(), propertyChargeableListActivityVo.getAmount());
    }

    /**
     * 计算收费项目的滞纳金
     *
     * @param propertyChargeableItemAmountVo 收费项目收费详情
     * @param overdueDays                    逾期天数
     * @param lateFeeRatio                   滞纳金比率
     * @return
     */
    public BigDecimal calItemOverdueAmount(PropertyChargeableItemAmountVo propertyChargeableItemAmountVo, Long overdueDays, BigDecimal lateFeeRatio) {
        propertyChargeableItemAmountVo.setOverdueAmount(BigDecimal.ZERO);
        propertyChargeableItemAmountVo.setOverdueDays(0);
        BigDecimal itemAmount = propertyChargeableItemAmountVo.getItemAmount();
        //如果为空 则不需要计算
        if (BigDecimal.ZERO.equals(itemAmount)) {
            return BigDecimal.ZERO;
        }
        //如果没有超过逾期时间则不计算
        if (overdueDays <= 0) {
            return BigDecimal.ZERO;
        }
        //计算滞纳金
        return propertyChargeableItemAmountVo.getItemAmount().multiply(lateFeeRatio).multiply(BigDecimal.valueOf(overdueDays)).multiply(BigDecimal.valueOf(0.01));
    }


    /**
     * 计算收费项目的收费金额
     *
     * @param timeLength                      时间长度
     * @param roomArea                        房间面积
     * @param propertyChargeableItemMethod    计费方式
     * @param propertyChargeableItemUnitPrice 单价
     * @return
     */

    public BigDecimal calItemAmount(Long timeLength,
                                    BigDecimal roomArea,
                                    Byte propertyChargeableItemMethod,
                                    BigDecimal propertyChargeableItemUnitPrice
    ) {
        //获取计算方式
        PropertyBillConstants.PropertyChargeableItemMethod method = PropertyBillConstants.PropertyChargeableItemMethod.getByMethod(propertyChargeableItemMethod);

        return method.calculate(propertyChargeableItemUnitPrice, roomArea, timeLength);
    }


    /**
     * 计算收费项目的收费金额
     *
     * @param timeLength                       时间长度
     * @param overdueDays                      逾期天数
     * @param roomArea                         房间面积
     * @param propertyChargeableItemDto        收费项目
     * @param propertyChargeableListActivityVo 项目活动
     * @return
     */
    public PropertyChargeableItemAmountVo calAmount(Long timeLength,
                                                    Long overdueDays,
                                                    BigDecimal roomArea,
                                                    PropertyChargeableItemDto propertyChargeableItemDto,
                                                    PropertyChargeableListActivityVo propertyChargeableListActivityVo
    ) {
        //计算项目计费
        BigDecimal itemAmount = calItemAmount(timeLength, roomArea, propertyChargeableItemDto.getMethod(), propertyChargeableItemDto.getUnitPrice());

        PropertyChargeableItemAmountVo propertyChargeableItemAmountVo = new PropertyChargeableItemAmountVo();
        propertyChargeableItemAmountVo.setItemAmount(adjustScale(itemAmount, SCALE));
        propertyChargeableItemAmountVo.setPropertyChargeableItem(propertyChargeableItemDto);

        //计算折扣
        BigDecimal discountAmount = null;
        if (propertyChargeableListActivityVo != null) {
            discountAmount = calItemDiscountAmount(propertyChargeableItemAmountVo, propertyChargeableListActivityVo);
        } else {
            discountAmount = BigDecimal.ZERO;
        }
        propertyChargeableItemAmountVo.setDiscountAmount(adjustScale(discountAmount, SCALE));
        propertyChargeableItemAmountVo.setPropertyChargeableListActivityVo(propertyChargeableListActivityVo);

        //计算逾期
        BigDecimal calItemOverdueAmount = calItemOverdueAmount(propertyChargeableItemAmountVo, overdueDays, propertyChargeableItemDto.getLateFeeRatio());
        propertyChargeableItemAmountVo.setOverdueAmount(adjustScale(calItemOverdueAmount, SCALE));
        propertyChargeableItemAmountVo.setOverdueDays(overdueDays);

        //如果折扣金额大于项目金额 则项目金额为0
        if (propertyChargeableItemAmountVo.getDiscountAmount().compareTo(propertyChargeableItemAmountVo.getItemAmount()) > 0) {
            propertyChargeableItemAmountVo.setItemAmount(BigDecimal.ZERO);
        }

        //计算总金额
        BigDecimal totalAmount = propertyChargeableItemAmountVo.getItemAmount().subtract(propertyChargeableItemAmountVo.getDiscountAmount()).add(calItemOverdueAmount);
        propertyChargeableItemAmountVo.setTotalAmount(adjustScale(totalAmount, SCALE));

        return propertyChargeableItemAmountVo;
    }

    /**
     * 获取开始时间和结束时间
     *
     * @param startDate 账单开始时间
     * @param cycleType
     * @return
     */
    public Date getEndDate(Date startDate, PropertyBillConstants.PropertyChargeableListCycleTypeEnum cycleType) {

        Date endDate = null;
        switch (cycleType) {
            case MONTH:
                endDate = DateUtils.addMonths(startDate, 1);
                break;
            case SEASON:
                endDate = DateUtils.addMonths(startDate, 3);
                break;
            case HALF_YEAR:
                endDate = DateUtils.addMonths(startDate, 6);
                break;
            case YEAR:
                endDate = DateUtils.addMonths(startDate, 12);
                break;
            default:
        }
        return endDate;
    }

    /**
     * 获取开始时间所在的时间段和对应时间段的名称
     * 月:1,2,3,4,5,6,7,8,9,10,11,12
     * 季度:1,2,3,4
     * 半年:1,2
     * 年:1
     *
     * @param cycleType 收费周期
     * @param startDate 账单开始时间
     * @return
     */
    public Tuple2<Integer, String> getPeriodValueAndName(PropertyBillConstants.PropertyChargeableListCycleTypeEnum cycleType, Date startDate) {

        int periodValue = -1;
        String periodName = "";
        int month = DateUtil.month(startDate);
        switch (cycleType) {
            case MONTH:
                //month 从零开始
                periodValue = month + 1;
                periodName = "第" + month + "月";
                break;
            case SEASON:
                //第一季度
                if (month >= 0 && month <= 2) {
                    periodValue = 1;
                    periodName = "第一季度";
                } else if (month >= 3 && month <= 5) {
                    periodValue = 2;
                    periodName = "第二季度";
                } else if (month >= 6 && month <= 9) {
                    periodValue = 3;
                    periodName = "第三季度";
                } else if (month >= 10 && month <= 11) {
                    periodValue = 4;
                    periodName = "第四季度";
                }
                break;
            case HALF_YEAR:
                //半年
                if (month >= 0 && month <= 5) {
                    periodValue = 1;
                    periodName = "上半年";
                } else if (month >= 6 && month <= 12) {
                    periodValue = 2;
                    periodName = "下半年";
                }
                break;
            case YEAR:
                periodValue = 1;
                periodName = "全年";
                break;
            default:
        }

        return Tuples.of(periodValue, periodName);
    }

    /**
     * 1.获取账单开始和结束时间
     * 2.获取账单实际的月份数
     *
     * @param year      账单年份
     * @param cycleType 收费周期
     * @param startDate 账单开始时间
     * @return
     */
    public Tuple3<Date, Date, Integer> getStartAndEndDateAndSelfMonths(int year, PropertyBillConstants.PropertyChargeableListCycleTypeEnum cycleType, Date startDate) {

        int month = DateUtil.month(startDate);
        Date billStartDate = null;
        Date billEndDate = null;
        int selfMonths = 0;
        switch (cycleType) {
            case MONTH:
                selfMonths = 1;
                //month 从零开始
                //mysql时间保存有问题,当时间是"2023-04-01 23:59:59"时变成了"2023-04-02 00:00:00"
                billStartDate = DateUtil.beginOfMonth(startDate);
                billEndDate = DateUtil.endOfMonth(startDate).offset(DateField.MILLISECOND, -999);
                break;
            case SEASON:
                selfMonths = 3;
                //第一季度
                if (month >= 0 && month <= 2) {
                    billStartDate = com.quectel.util.common.DateUtils.parse(year + "-01-01", com.quectel.util.common.DateUtils.FormatType.SIMPLE);
                    billEndDate = com.quectel.util.common.DateUtils.parse(year + "-03-31", com.quectel.util.common.DateUtils.FormatType.SIMPLE);
                    selfMonths = 3;
                } else if (month >= 3 && month <= 5) {
                    billStartDate = com.quectel.util.common.DateUtils.parse(year + "-04-01", com.quectel.util.common.DateUtils.FormatType.SIMPLE);
                    billEndDate = com.quectel.util.common.DateUtils.parse(year + "-06-30", com.quectel.util.common.DateUtils.FormatType.SIMPLE);
                } else if (month >= 6 && month <= 9) {
                    billStartDate = com.quectel.util.common.DateUtils.parse(year + "-07-01", com.quectel.util.common.DateUtils.FormatType.SIMPLE);
                    billEndDate = com.quectel.util.common.DateUtils.parse(year + "-9-30", com.quectel.util.common.DateUtils.FormatType.SIMPLE);
                } else if (month >= 10 && month <= 11) {
                    billStartDate = com.quectel.util.common.DateUtils.parse(year + "-10-01", com.quectel.util.common.DateUtils.FormatType.SIMPLE);
                    billEndDate = com.quectel.util.common.DateUtils.parse(year + "-12-31", com.quectel.util.common.DateUtils.FormatType.SIMPLE);
                }
                break;
            case HALF_YEAR:
                selfMonths = 6;
                //半年
                if (month >= 0 && month <= 5) {
                    billStartDate = com.quectel.util.common.DateUtils.parse(year + "-01-01", com.quectel.util.common.DateUtils.FormatType.SIMPLE);
                    billEndDate = com.quectel.util.common.DateUtils.parse(year + "-06-30", com.quectel.util.common.DateUtils.FormatType.SIMPLE);
                } else if (month >= 6 && month <= 12) {
                    billStartDate = com.quectel.util.common.DateUtils.parse(year + "-07-01", com.quectel.util.common.DateUtils.FormatType.SIMPLE);
                    billEndDate = com.quectel.util.common.DateUtils.parse(year + "-12-31", com.quectel.util.common.DateUtils.FormatType.SIMPLE);
                }
                break;
            case YEAR:
                selfMonths = 12;
                billStartDate = com.quectel.util.common.DateUtils.parse(year + "-01-01", com.quectel.util.common.DateUtils.FormatType.SIMPLE);
                billEndDate = com.quectel.util.common.DateUtils.parse(year + "-12-13", com.quectel.util.common.DateUtils.FormatType.SIMPLE);
                break;
            default:
        }

        return Tuples.of(billStartDate, billEndDate, selfMonths);
    }


    /**
     * @param billCycleDateInfoVo               账单日期
     * @param villageRoom                       房间
     * @param propertyChargeableList            收费标注
     * @param propertyChargeableListActivityDto 活动详情
     * @return
     */
    public PropertyBillAmountVo calPropertyBillAmount(
            PropertyBillCycleDateInfoVo billCycleDateInfoVo,
            VillageRoomDto villageRoom,
            PropertyChargeableListDto propertyChargeableList,
            PropertyChargeableListActivityDto propertyChargeableListActivityDto

    ) {

        PropertyBillConstants.PropertyChargeableListCycleTypeEnum periodType = PropertyBillConstants.PropertyChargeableListCycleTypeEnum.getByType(propertyChargeableList.getCycleType());
        if (periodType == null) {
            log.error("calPropertyBillAmount method : getPropertyChargeableListCycleType is null");
            throw new CalPropertyBillException();
        }
        if (villageRoom.getRoomArea() == null) {
            log.error("villageRoom roomArea is null ,roomId:{}", villageRoom.getId());
            throw new CalPropertyBillException();
        }

        //活动处理
        Map<Long, PropertyChargeableListActivityVo> activityVoMap = new HashMap<>();
        List<PropertyChargeableListActivityVo> gifts = new ArrayList<>();
        Integer giveMonths = 0;
        if (propertyChargeableListActivityDto != null) {
            giveMonths = propertyChargeableListActivityDto.getGiveMonths();
            //活动分组 折扣或者礼品
            List<PropertyChargeableListActivityVo> propertyChargeableListActivityVos = JacksonUtils.parseArray(propertyChargeableListActivityDto.getDiscountConfigs(), PropertyChargeableListActivityVo.class);
            if (CollectionUtils.isNotEmpty(propertyChargeableListActivityVos)) {
                for (PropertyChargeableListActivityVo propertyChargeableListActivityVo : propertyChargeableListActivityVos) {
                    //收费项目
                    if (PropertyBillConstants.ActivityGroupType.DISCOUNT_ITEM.getType().equals(propertyChargeableListActivityVo.getGroupType())) {
                        activityVoMap.put(Long.valueOf(propertyChargeableListActivityVo.getConfigId()), propertyChargeableListActivityVo);
                    } else if (PropertyBillConstants.ActivityGroupType.GIFT.getType().equals(propertyChargeableListActivityVo.getGroupType())) {
                        //赠送礼品
                        gifts.add(propertyChargeableListActivityVo);
                    }
                }
            }

        }
        //赠送月份
        billCycleDateInfoVo.setGiveMonths(giveMonths);

        List<PropertyChargeableItemAmountVo> chargeableItemAmountVos = new ArrayList<>();
        for (PropertyChargeableItemDto propertyChargeableItemDto : propertyChargeableList.getItemList()) {
            //计算单个项目的具体全部收费情况
            //计算账期时间
            Long itemChargingLength = getItemChargingLength(billCycleDateInfoVo, propertyChargeableItemDto.getMethod());
            PropertyChargeableItemAmountVo propertyChargeableItemAmountVo = calAmount(itemChargingLength, billCycleDateInfoVo.getOverdueDays(), villageRoom.getRoomArea(), propertyChargeableItemDto, activityVoMap.get(propertyChargeableItemDto.getId()));
            chargeableItemAmountVos.add(propertyChargeableItemAmountVo);
        }


        //收费项目汇总
        BigDecimal totalAmount = BigDecimal.ZERO;
        BigDecimal itemAmount = BigDecimal.ZERO;
        BigDecimal discountAmount = BigDecimal.ZERO;
        BigDecimal overdueAmount = BigDecimal.ZERO;
        for (PropertyChargeableItemAmountVo chargeableItemAmountVo : chargeableItemAmountVos) {
            totalAmount = totalAmount.add(chargeableItemAmountVo.getTotalAmount());
            itemAmount = itemAmount.add(chargeableItemAmountVo.getItemAmount());
            discountAmount = discountAmount.add(chargeableItemAmountVo.getDiscountAmount());
            overdueAmount = overdueAmount.add(chargeableItemAmountVo.getOverdueAmount());
        }

        //封装返回
        PropertyBillAmountVo propertyBillAmountVo = new PropertyBillAmountVo();
        propertyBillAmountVo.setVillageRoom(villageRoom);
        propertyBillAmountVo.setPropertyChargeableList(propertyChargeableList);
        propertyBillAmountVo.setPropertyChargeableListActivity(propertyChargeableListActivityDto);
        propertyBillAmountVo.setBillCycleDateInfoVo(billCycleDateInfoVo);
        propertyBillAmountVo.setGiftList(gifts);
        propertyBillAmountVo.setChargeableItemAmountVos(chargeableItemAmountVos);
        propertyBillAmountVo.setTotalAmount(totalAmount);
        propertyBillAmountVo.setItemTotalAmount(itemAmount);
        propertyBillAmountVo.setDiscountAmount(discountAmount);
        propertyBillAmountVo.setOverdueAmount(overdueAmount);

        return propertyBillAmountVo;
    }

    /**
     * 查询当前可用的活动
     *
     * @param tenantId
     * @param villageId
     * @param chargeableListId
     * @param year
     * @param periodType
     * @param periodValue
     * @return
     */
    public List<PropertyChargeableListActivityDto> getCurrentActivity(Long tenantId, Long villageId, Long chargeableListId, Integer year, Byte periodType, Integer periodValue) {
        //根据条件查询
        Map<String, Object> params = new HashMap<>();
        params.put(LambdaUtil.getFieldName(PropertyChargeableListActivityDto::getTenantId), tenantId);
        params.put(LambdaUtil.getFieldName(PropertyChargeableListActivityDto::getVillageId), villageId);
        params.put(LambdaUtil.getFieldName(PropertyChargeableListActivityDto::getChargeableListId), chargeableListId);
        params.put(LambdaUtil.getFieldName(PropertyChargeableListActivityDto::getYear), year);
        params.put(LambdaUtil.getFieldName(PropertyChargeableListActivityDto::getPeriod), periodType);
        params.put(LambdaUtil.getFieldName(PropertyChargeableListActivityDto::getStatus), SystemConstants.NORMAL);
        params.put(LambdaUtil.getFieldName(PropertyBillDto::getPeriodValue), String.valueOf(periodValue));
        params.put(PropertyBillConstants.TODAY, com.quectel.util.common.DateUtils.format(new Date(), com.quectel.util.common.DateUtils.FormatType.SIMPLE));
        return propertyChargeableListActivityService.queryList(params);
    }

    public BigDecimal adjustScale(BigDecimal bigDecimal, int scale) {
        return bigDecimal.setScale(scale, RoundingMode.HALF_UP);
    }


    /**
     * 计算收费周期的开始和结束时间,计费周期的时间长度
     *
     * @param startDate   新账单开始的日期
     * @param cycleType   收费标准收费周期类型
     * @param overdueDays 可逾期天数,从结束日期开始计算
     * @return
     */
    public PropertyBillCycleDateInfoVo getBillCycleDateInfo(String chargeableListName, Date startDate, PropertyBillConstants.PropertyChargeableListCycleTypeEnum cycleType, Integer overdueDays) {
        //获取年份
        int year = DateUtil.year(startDate);
        //新生成账单所在周期和周期名称
        Tuple2<Integer, String> periodValue = getPeriodValueAndName(cycleType, startDate);
        Tuple3<Date, Date, Integer> startAndEndDateAndSelfMonths = getStartAndEndDateAndSelfMonths(year, cycleType, startDate);

        Date now = new Date();

        //逾期时间从结束时间往后加天数
        Date overdueDate = DateUtils.addDays(startAndEndDateAndSelfMonths.getT2(), overdueDays);
        //如果现在的时间超过了逾期时间,则计算逾期天数
        long between = 0;
        if (now.after(DateUtil.endOfDay(overdueDate))) {
            between = DateUtil.betweenDay(now, overdueDate, true);
        }

        PropertyBillCycleDateInfoVo propertyBillCycleDateInfoVo = new PropertyBillCycleDateInfoVo();
        propertyBillCycleDateInfoVo.setYear(DateUtil.year(startDate));
        propertyBillCycleDateInfoVo.setPeriodType(cycleType.getType());
        propertyBillCycleDateInfoVo.setPeriodValue(periodValue.getT1());
        propertyBillCycleDateInfoVo.setPeriodName(propertyBillCycleDateInfoVo.getYear() + "年" + periodValue.getT2() + chargeableListName);
        propertyBillCycleDateInfoVo.setStartDate(startAndEndDateAndSelfMonths.getT1());
        propertyBillCycleDateInfoVo.setEndDate(startAndEndDateAndSelfMonths.getT2());
        propertyBillCycleDateInfoVo.setSelfMonths(startAndEndDateAndSelfMonths.getT3());
        propertyBillCycleDateInfoVo.setOverdueDate(overdueDate);
        propertyBillCycleDateInfoVo.setOverdueDays(between);

        if (propertyBillCycleDateInfoVo.getOverdueDays() > 0) {
            propertyBillCycleDateInfoVo.setOverdueStatus(PropertyBillConstants.OverdueStatus.OVERDUE.getStatus());
        } else {
            propertyBillCycleDateInfoVo.setOverdueStatus(PropertyBillConstants.OverdueStatus.NOT_OVERDUE.getStatus());
        }
        return propertyBillCycleDateInfoVo;
    }

    /**
     * 计算收费周期的开始和结束时间,计费周期的时间长度
     *
     * @param billCycleDateInfoVo          账单时间对象
     * @param propertyChargeableItemMethod 计费方式
     * @return
     */
    public Long getItemChargingLength(PropertyBillCycleDateInfoVo billCycleDateInfoVo, Byte propertyChargeableItemMethod) {

        //时间长度
        PropertyBillConstants.TimeUnitEnum timeUnitEnum = null;

        PropertyBillConstants.PropertyChargeableItemMethod method = PropertyBillConstants.PropertyChargeableItemMethod.getByMethod(propertyChargeableItemMethod);
        if (PropertyBillConstants.PropertyChargeableItemMethod.AREA_PER_DAY.equals(method)
                || PropertyBillConstants.PropertyChargeableItemMethod.FIXED_AMOUNT_PER_DAY.equals(method)) {
            timeUnitEnum = PropertyBillConstants.TimeUnitEnum.DAY;
        }

        if (PropertyBillConstants.PropertyChargeableItemMethod.AREA_PER_MONTH.equals(method)
                || PropertyBillConstants.PropertyChargeableItemMethod.FIXED_AMOUNT_PER_MONTH.equals(method)) {
            timeUnitEnum = PropertyBillConstants.TimeUnitEnum.MONTH;
        }
        if (timeUnitEnum == null) {
            throw new GetBillLengthErrorException();
        }

        long length = 0;
        //目前只支持按天和按月,后续补充可扩展
        switch (timeUnitEnum) {
            case DAY:
                //加上
                length = DateUtil.between(billCycleDateInfoVo.getStartDate(), billCycleDateInfoVo.getEndDate(), DateUnit.DAY) + 1;
                break;
            case MONTH:
                length = billCycleDateInfoVo.getSelfMonths();
                break;
            default:
                return null;
        }
        return length;
    }

}
